%% @author Mochi Media <dev@mochimedia.com>
%% @copyright 2010 Mochi Media <dev@mochimedia.com>

%% @doc Ensure that the relatively-installed dependencies are on the code
%%      loading path, and locate resources relative
%%      to this application's path.

-module(yzb_deps).
-author("Mochi Media <dev@mochimedia.com>").

-export([ensure/0, ensure/1]).
-export([get_base_dir/0, get_base_dir/1]).
-export([local_path/1, local_path/2]).
-export([deps_on_path/0, new_siblings/1]).

%% @spec ensure() -> ok
%% @doc Ensure that the ebin and include paths for dependencies of
%%      this application are on the code path. Equivalent to
%%      ensure(?Module).
ensure() ->
    ensure(?MODULE).

%% @spec ensure(Module) -> ok
%% @doc Ensure that all ebin and include paths for dependencies
%%      of the application for Module are on the code path.
ensure(Module) ->
    % 添加代码目录
    code:add_paths(new_siblings(Module)),
    % 显示命名冲突报告
    code:clash(),
    ok.

%% @spec new_siblings(Module) -> [Dir]
%% @doc Find new siblings paths relative to Module that aren't already on the
%%      code path.
new_siblings(Module) ->
    Existing = deps_on_path(),
    % 使用Unix风格的通配符匹配文件
    Path = local_path(["deps", "*", "ebin"], Module),
    SiblingEbin = filelib:wildcard(Path),
    Siblings = [
                    filename:dirname(X) || X <- SiblingEbin,
                    % is_element(Element, Ordset) -> bool()
                    % Returns true if Element is an element of Ordset, otherwise false.
                    ordsets:is_element(
                        filename:basename(filename:dirname(X)),
                        Existing
                    ) =:= false
                ],
    % lists:filter(Pred, List1) -> List2
    % 用 Pred 条件过滤列表List1
    % filelib:is_dir(Name) -> true | false 测试Name是否为目录
    lists:filter(
                    fun filelib:is_dir/1,
                    lists:append([
                                    [filename:join([X, "ebin"]), filename:join([X, "include"])]
                                    || X <- Siblings
                                 ])
                ).


%% @spec deps_on_path() -> [ProjNameAndVers]
%% @doc List of project dependencies on the path.
deps_on_path() ->
    F = fun (X, Acc) ->
                ProjDir = filename:dirname(X),

                case {filename:basename(X),
                    % Forexample: “deps/mochiweb/ebin”
                    % 去除中间目录 "mochiweb",即获取目标模块“mochiweb”
                      filename:basename(filename:dirname(ProjDir))} of
                    {"ebin", "deps"} ->
                        [filename:basename(ProjDir) | Acc];
                    _ ->
                        Acc
                end
        end,
    % ordsets:from_list(List) -> Ordset
    % Returns an ordered set of the elements in List
    % foldl(Fun(Elem, AccIn), Acc0, List) -> Acc1　　
    % AccIn初使为Acc0, Elem是List中的值，把List中的所有值都执行函数一次得到列表
    Result = ordsets:from_list(lists:foldl(F, [], code:get_path())),
    Result.

%% @spec get_base_dir(Module) -> string()
%% @doc Return the application directory for Module. It assumes Module is in
%%      a standard OTP layout application in the ebin or src directory.
get_base_dir(Module) ->
    % 加载模块路径
    {file, Here} = code:is_loaded(Module),
    filename:dirname(filename:dirname(Here)).

%% @spec get_base_dir() -> string()
%% @doc Return the application directory for this application. Equivalent to
%%      get_base_dir(?MODULE).
get_base_dir() ->
    get_base_dir(?MODULE).

%% @spec local_path(Components) -> string()
%% @doc Return an application-relative directory for this application.
%%      Equivalent to local_path(Components, ?MODULE).
local_path(Components) ->
    local_path(Components, ?MODULE).

%% @spec local_path([string()], Module) -> string()
%% @doc Return an application-relative directory from Module's application.
local_path(Components, Module) ->
    % filename:join（Components) -> string()
    % 将 Components 列表中的元素组成一个路径
    filename:join([get_base_dir(Module) | Components]).
